<?php

class db_mysqli {

    public $conf = array(); // 配置，可以支持主从
    public $rconf = array(); // 配置，可以支持主从
    public $wlink = NULL;  // 写连接
    public $rlink = NULL;  // 读连接
    public $link = NULL;   // 最后一次使用的连接
    public $errno = 0;
    public $errstr = '';
    public $sqls = array();
    public $tablepre = '';
    public $innodb_first = TRUE;// 优先 InnoDB
    public $charset = 'utf8';//用户配置的字符集

    public function __construct($conf){
        $this->conf = $conf;
        if(DEBUG < 2) mysqli_report(MYSQLI_REPORT_OFF);// 因为PHP8.1开始，mysqli_report的默认值为MYSQLI_REPORT_ERROR，所以必须手动设置错误报告模式为不抛出异常
        $this->tablepre = $conf['master']['tablepre'];
        if(!empty($conf['master']['charset'])) $this->charset = $conf['master']['charset'];
    }

    // 根据配置文件连接
    public function connect(){
        $this->wlink = $this->connect_master();
        $this->rlink = $this->connect_slave();
        return $this->wlink && $this->rlink;
    }

    // 连接写服务器
    public function connect_master(){
        if ($this->wlink && mysqli_ping($this->wlink)) return $this->wlink;
        $conf = $this->conf['master'];
        $this->wlink = $this->real_connect($conf['host'], $conf['user'], $conf['password'], $conf['name'], $conf['charset'], $conf['engine']);
        return $this->wlink;
    }

    // 连接从服务器，如果有多台，则随机挑选一台，如果为空，则与主服务器一致。
    public function connect_slave(){
        if ($this->rlink && mysqli_ping($this->rlink)) return $this->rlink;
        if (empty($this->conf['slaves'])) {
            $this->wlink = $this->connect_master();
            $this->rlink = $this->wlink;
            $this->rconf = $this->conf['master'];
        } else {
            $n = array_rand($this->conf['slaves']);
            $conf = $this->conf['slaves'][$n];
            $this->rconf = $conf;
            $this->rlink = $this->real_connect($conf['host'], $conf['user'], $conf['password'], $conf['name'], $conf['charset'], $conf['engine']);
        }
        return $this->rlink;
    }

    public function real_connect($host, $user, $password, $name, $charset = '', $engine = ''){
        $link = @mysqli_connect($host, $user, $password, $name);
        if (!$link) {
            $this->error(1024, lang('connect_db_fail:') . mysqli_error());
            return FALSE;
        }
        if (!mysqli_set_charset($link, $charset)) {
            $this->error(mysqli_errno($link), lang('charset_set_fail:') . mysqli_error($link));
            mysqli_close($link);
            return FALSE;
        }
        return $link;
    }

    public function sql_find_one($sql){
        $query = $this->query($sql);
        if (!$query) return $query;
        $r = mysqli_fetch_assoc($query);
        mysqli_free_result($query);
        return $r === FALSE ? NULL : $r;
    }


    public function sql_find($sql, $key = NULL){
        $query = $this->query($sql);
        if (!$query) return $query;
        $arrlist = array();
        while ($arr = mysqli_fetch_assoc($query)) {
            $key ? $arrlist[$arr[$key]] = $arr : $arrlist[] = $arr;// 顺序没有问题，尽管是数字，仍然是有序的，看来内部实现是链表，与 js 数组不同。
        }
        mysqli_free_result($query);
        return $arrlist;
    }

    public function find($table, $cond = array(), $orderby = array(), $page = 1, $pagesize = 10, $key = '', $col = array()){
        $page = max(1, $page);
        $cond = db_cond_to_sqladd($cond);
        $orderby = db_orderby_to_sqladd($orderby);
        $offset = ($page - 1) * $pagesize;
        $cols = $col ? implode(',', $col) : '*';
        return $this->sql_find("SELECT $cols FROM {$this->tablepre}$table $cond$orderby LIMIT $offset,$pagesize", $key);

    }

    public function find_one($table, $cond = array(), $orderby = array(), $col = array()){
        $cond = db_cond_to_sqladd($cond);
        $orderby = db_orderby_to_sqladd($orderby);
        $cols = $col ? implode(',', $col) : '*';
        return $this->sql_find_one("SELECT $cols FROM {$this->tablepre}$table $cond$orderby LIMIT 1");
    }

    public function query($sql, $link = NULL){
        if (!$link) {
            if (!$this->rlink && !$this->connect_slave()) return FALSE;
            $link = $this->link = $this->rlink;
        }
        $t1 = microtime(1);
        $query = mysqli_query($link, $sql);
        $t2 = microtime(1);
        if ($query === FALSE) $this->error(mysqli_errno($link), mysqli_error($link));

        $t3 = substr($t2 - $t1, 0, 6);
        DEBUG AND xn_log("[$t3]" . $sql, 'db_sql');
        if (count($this->sqls) < 1000) $this->sqls[] = "[$t3]" . $sql;

        return $query;
    }

    public function exec($sql, $link = NULL){
        if (!$link) {
            if (!$this->wlink && !$this->connect_master()) return FALSE;
            $link = $this->link = $this->wlink;
        }
        if (strtoupper(substr($sql, 0, 12)) == 'CREATE TABLE') {
            $fulltext = preg_match('/\bFULLTEXT\s*\(/is',$sql) > 0;
            $highversion = version_compare($this->version(), '5.6') >= 0;
            if (!$fulltext || ($fulltext && $highversion)) {
                $conf = $this->conf['master'];
                if (strtolower($conf['engine']) != 'myisam') {
                    $this->innodb_first AND $this->is_support_innodb() AND $sql = str_ireplace('MyISAM', 'InnoDB', $sql);
                }
            }
            //应用用户配置的字符集编码：
            if ($this->charset != 'utf8' && $this->is_support_charset($this->charset)) {
                $charset = $this->charset;
                $sql = preg_replace_callback('/\b((CHARSET|COLLATE)\s*\=\s*)utf8(?=\b|_)/is', function ($matches) use ($charset) {
                    return $matches[1].$charset;
                }, $sql);
                unset($charset);
            }
        }
        $t1 = microtime(1);
        $query = mysqli_query($link, $sql);
        $t2 = microtime(1);
        $t3 = substr($t2 - $t1, 0, 6);

        DEBUG AND xn_log("[$t3]" . $sql, 'db_sql');
        if (count($this->sqls) < 1000) $this->sqls[] = "[$t3]" . $sql;

        if ($query !== FALSE) {
            $pre = strtoupper(substr(trim($sql), 0, 7));
            if ($pre == 'INSERT ' || $pre == 'REPLACE') {
                return mysqli_insert_id($link);
            } elseif ($pre == 'UPDATE ' || $pre == 'DELETE ') {
                return mysqli_affected_rows($link);
            }
        } else {
            $this->error(mysqli_errno($link), mysqli_error($link));
        }

        return $query;
    }

    // 如果为 innodb，条件为空，并且有权限读取 information_schema
    public function count($table, $cond = array()){
        $this->connect_slave();
        if (empty($cond) && $this->rconf['engine'] == 'innodb') {
            $dbname = $this->rconf['name'];
            $sql = "SELECT TABLE_ROWS as num FROM information_schema.tables WHERE TABLE_SCHEMA='$dbname' AND TABLE_NAME='$table'";
        } else {
            $cond = db_cond_to_sqladd($cond);
            $sql = "SELECT COUNT(*) AS num FROM `$table` $cond";
        }
        $arr = $this->sql_find_one($sql);
        return !empty($arr) ? intval($arr['num']) : $arr;
    }

    public function maxid($table, $field, $cond = array()){
        $sqladd = db_cond_to_sqladd($cond);
        $sql = "SELECT MAX($field) AS maxid FROM `$table` $sqladd";
        $arr = $this->sql_find_one($sql);
        return !empty($arr) ? intval($arr['maxid']) : $arr;
    }

    public function truncate($table){
        return $this->exec("TRUNCATE $table");
    }

    public function close(){
        $r = true;
        if ($this->rlink && $this->wlink != $this->rlink) {
            $r = mysqli_close($this->rlink);
            $this->rlink = NULL;
        }
        if ($this->wlink) {
            $r = mysqli_close($this->wlink);
            $this->wlink = NULL;
        }
        return $r;
    }

    public function version(){
        $r = $this->sql_find_one("SELECT VERSION() AS v");
        return $r['v'];
    }

    public function error($errno = 0, $errstr = ''){
        $this->errno = $errno ? $errno : ($this->link ? mysqli_errno($this->link) : mysqli_errno());
        $this->errstr = $errstr ? $errstr : ($this->link ? mysqli_error($this->link) : mysqli_error());
        DEBUG AND trigger_error('Database Error:' . $this->errstr);
    }

    public function is_support_innodb(){
        static $ret = null;//使用缓存以提高效率
        if ($ret === null) {
            $arrlist = $this->sql_find('SHOW ENGINES');
            $ret = array_search('InnoDB', array_column($arrlist, 'Engine')) !== false;
        }
        return $ret;
    }

    public function is_support_charset($charset){
        static $cs = [];//使用缓存以提高效率
        if (!isset($cs[$charset])) {
            $r = $this->sql_find_one("SHOW CHARACTER SET like '{$charset}'");
            $cs[$charset] = false;
            if (!empty($r)) {
                $cs[$charset] = isset($r['Default collation']) ? $r['Default collation'] : true;
            }
        }
        return $cs[$charset];
    }

    public function __destruct(){
        $this->close();
    }
}
?>